Passed
Pull Request — filestream (#169)
by
unknown
01:47
created

file-write.ts ➔ streamOriginalIntoNewFileAsync   A

Complexity

Conditions 3

Size

Total Lines 10
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 10
rs 9.95
c 0
b 0
f 0
cc 3
1
import {
2
    fsWritePromise,
3
    fillBufferAsync,
4
    fillBufferSync,
5
    processFileSync,
6
    processFileAsync,
7
    fsExistsPromise,
8
    fsWriteFilePromise,
9
    fsRenamePromise,
10
    unlinkIfExistSync,
11
    unlinkIfExist
12
} from "./util-file"
13
import * as fs from 'fs'
14
import { findId3TagPosition, getId3TagSize } from "./id3-tag"
15
import { WriteCallback, WriteOptions } from "./types/write"
16
17
const DefaultFileBufferSize = 20 * 1024 * 1024
18
19
export function writeId3TagToFileSync(
20
    filepath: string,
21
    id3Tag: Buffer,
22
    options: WriteOptions
23
): void {
24
    if (!fs.existsSync(filepath)) {
25
        fs.writeFileSync(filepath, id3Tag)
26
        return
27
    }
28
    const tempFilepath = makeTempFilepath(filepath)
29
    processFileSync(filepath, 'r', (readFileDescriptor) => {
30
        try {
31
            processFileSync(tempFilepath, 'w', (writeFileDescriptor) => {
32
                fs.writeSync(writeFileDescriptor, id3Tag)
33
                copyFileWithoutId3TagSync(
34
                    readFileDescriptor,
35
                    writeFileDescriptor,
36
                    options.fileBufferSize ?? DefaultFileBufferSize
37
                )
38
            })
39
        } catch(error) {
40
            unlinkIfExistSync(tempFilepath)
41
            throw error
42
        }
43
    })
44
    fs.renameSync(tempFilepath, filepath)
45
}
46
47
export function writeId3TagToFile(
48
    filepath: string,
49
    id3Tag: Buffer,
50
    options: WriteOptions,
51
    callback: WriteCallback
52
): void {
53
    writeId3TagToFileAsync(filepath, id3Tag, options)
54
    .then(
55
        () => callback(null),
56
        (error) => callback(error)
57
    )
58
}
59
60
export async function writeId3TagToFileAsync(
61
    filepath: string,
62
    id3Tag: Buffer,
63
    options: WriteOptions
64
): Promise<void> {
65
    if (!await fsExistsPromise(filepath)) {
66
        await fsWriteFilePromise(filepath, id3Tag)
67
        return
68
    }
69
    const tempFilepath = makeTempFilepath(filepath)
70
    await processFileAsync(filepath, 'r', async (readFileDescriptor) => {
71
        try {
72
            await processFileAsync(tempFilepath, 'w',
73
                async (writeFileDescriptor) => {
74
                    await fsWritePromise(writeFileDescriptor, id3Tag)
75
                    await copyFileWithoutId3TagAsync(
76
                        readFileDescriptor,
77
                        writeFileDescriptor,
78
                        options.fileBufferSize ?? DefaultFileBufferSize
79
                    )
80
                }
81
            )
82
        } catch(error) {
83
            await unlinkIfExist(tempFilepath)
84
            throw error
85
        }
86
87
    })
88
    await fsRenamePromise(tempFilepath, filepath)
89
}
90
91
function makeTempFilepath(filepath: string) {
92
    return `${filepath}.tmp-${Date.now()}`
93
}
94
95
function copyFileWithoutId3TagSync(
96
    readFileDescriptor: number,
97
    writeFileDescriptor: number,
98
    fileBufferSize: number
99
) {
100
    const buffer = Buffer.alloc(fileBufferSize)
101
    let readData
102
    while((readData = fillBufferSync(readFileDescriptor, buffer)).length) {
103
        const { data, bytesToSkip } = removeId3TagIfFound(readData)
104
        if (bytesToSkip) {
105
            fillBufferSync(readFileDescriptor, Buffer.alloc(bytesToSkip))
106
        }
107
        fs.writeSync(writeFileDescriptor, data, 0, data.length, null)
108
    }
109
}
110
111
async function copyFileWithoutId3TagAsync(
112
    readFileDescriptor: number,
113
    writeFileDescriptor: number,
114
    fileBufferSize: number
115
) {
116
    const buffer = Buffer.alloc(fileBufferSize)
117
    let readData
118
    while((readData = await fillBufferAsync(readFileDescriptor, buffer)).length) {
119
        const { data, bytesToSkip } = removeId3TagIfFound(readData)
120
        if (bytesToSkip) {
121
            await fillBufferAsync(readFileDescriptor, Buffer.alloc(bytesToSkip))
122
        }
123
        await fsWritePromise(writeFileDescriptor, data, 0, data.length, null)
124
    }
125
}
126
127
function removeId3TagIfFound(data: Buffer) {
128
    const id3TagPosition = findId3TagPosition(data)
129
    if (id3TagPosition === -1) {
130
        return { data }
131
    }
132
    const dataFromId3Start = data.subarray(id3TagPosition)
133
    const id3TagSize = getId3TagSize(dataFromId3Start)
134
    return {
135
        data: Buffer.concat([
136
            data.subarray(0, id3TagPosition),
137
            dataFromId3Start.subarray(Math.min(id3TagSize, dataFromId3Start.length))
138
        ]),
139
        bytesToSkip: Math.max(0, id3TagSize - dataFromId3Start.length)
140
    }
141
}
142